/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar.lualib;

import java.net.MalformedURLException;
import java.net.URL;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.ThreeArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;

import com.google.gson.JsonObject;
import com.lucky_byte.pay.jar.HttpClient;
import com.lucky_byte.pay.jar.p071.Fowder071;
import com.lucky_byte.pay.jar.p071.Lib071;
import com.lucky_byte.pay.jar.p071.Packet071;

/**
 * 银联 ACP Lua 模块
 */
public class LuaLib071 extends TwoArgFunction
{
	private static final Logger logger = LogManager.getLogger();

	@Override
	public LuaValue call(LuaValue modname, LuaValue env) {
		LuaValue library = tableOf();
		env.set("lib071", library);

		library.set("_new_packet", new NewPacket());
		library.set("_forward", new Forward());

		library.set("encode", new Encode());
		library.set("decode", new Decode());

		library.set("sign", new Sign());
		library.set("verify", new Verify());
		library.set("post", new Post());

		return library;
	}

	/**
	 * 创建一个新的空白报文
	 */
	class NewPacket extends ZeroArgFunction
	{
		@Override
		public LuaValue call() {
			Packet071 packet = new Packet071();
			return new LuaPacket(packet).call();
		}
	}

	/**
	 * 转发报文，报文可能来自于客户端，或者由平台自己构造，之间有一些细微的区别:
	 *
	 * 对于平台构造的报文，转发时总是需要使用 packBytes() 方法来重新生成网络字节流，
	 * 因此这需要终端主密钥和工作密钥的支持。
	 *
	 * 如果是转发来自 POS 终端的报文，那么需要进一步区分，如果是透传模式，
	 * 也就是说直接将终端发送的报文转发到银联，这种情况下，不能通过packBytes()对
	 * 报文进行重组，因为重组后的字节流有可能和终端发送的字节流不一样，另外重组需要
	 * 密钥的支持。
	 * 如果是间连模式，那么就需要使用 packBytes() 进行报文重组，这和上面提到的
	 * 转发平台构造的报文类似。
	 */
	class Forward extends TwoArgFunction
	{
		@Override
		public LuaValue call(LuaValue arg1, LuaValue arg2) {
			LuaTable lua_packet = arg1.checktable();
			Packet071 packet = (Packet071) lua_packet.get("_object")
					.checkuserdata(Packet071.class);
			
			// 第二个参数指示是否透传，true: 间连，false: 透传
			// 这个参数是可选的，如果省略，取值 true: 间连
			boolean indirect = arg2.optboolean(true);

			// 进行转发操作
			Fowder071 fowder = new Fowder071();
			Packet071 respkt = fowder.forward(packet, indirect);

			// 通过一个 Lua table 返回多个值
			LuaTable retval = LuaValue.tableOf();

			JsonObject netinfo = fowder.getNetinfo();
			if (netinfo == null) {
				logger.warn("转发后未取得网络通讯信息，请检查.");
				retval.set("netinfo", LuaValue.valueOf("{}"));
			} else {
				retval.set("netinfo", LuaValue.valueOf(netinfo.toString()));
			}
			if (respkt != null) {
				retval.set("respkt", new LuaPacket(respkt).call());
			}
			return retval;
		}
	}

	/**
	 * 对报文进行编码
	 */
	class Encode extends OneArgFunction
	{
		@Override
		public LuaValue call(LuaValue arg) {
			LuaTable lua_packet = arg.checktable();
			Packet071 packet = (Packet071) lua_packet.get("_object")
					.checkuserdata(Packet071.class);

			Packet071 clone = (Packet071) packet.clone();
			if (!Lib071.encode(clone)) {
				return LuaValue.NIL;
			}
			return new LuaPacket(clone).call();
		}
	}

	/**
	 * 对报文进行解码
	 */
	class Decode extends OneArgFunction
	{
		@Override
		public LuaValue call(LuaValue arg) {
			LuaTable lua_packet = arg.checktable();
			Packet071 packet = (Packet071) lua_packet.get("_object")
					.checkuserdata(Packet071.class);

			Packet071 clone = (Packet071) packet.clone();
			if (!Lib071.decode(clone)) {
				return LuaValue.NIL;
			}
			return new LuaPacket(clone).call();
		}
	}

	/**
	 * 对报文进行签名
	 */
	class Sign extends TwoArgFunction
	{
		/**
		 * @param arg1 Lua Packet 对象
		 * @param arg2 商户 UUID
		 */
		@Override
		public LuaValue call(LuaValue arg1, LuaValue arg2) {
			LuaTable lua_packet = arg1.checktable();
			Packet071 packet = (Packet071) lua_packet.get("_object")
					.checkuserdata(Packet071.class);

			String mer_uuid = arg2.checkjstring();
			Lib071.loadMerCerts(mer_uuid);

			String sign_key = mer_uuid + "-sign";
			return LuaValue.valueOf(Lib071.sign(packet, sign_key));
		}
	}

	/**
	 * 验证报文签名
	 */
	class Verify extends TwoArgFunction
	{
		/**
		 * @param arg1 Lua Packet 对象
		 * @param arg2 商户 UUID
		 */
		@Override
		public LuaValue call(LuaValue arg1, LuaValue arg2) {
			LuaTable lua_packet = arg1.checktable();
			Packet071 packet = (Packet071) lua_packet.get("_object")
					.checkuserdata(Packet071.class);

			String mer_uuid = arg2.checkjstring();
			Lib071.loadMerCerts(mer_uuid);

			String verify_key = mer_uuid + "-verify";
			return LuaValue.valueOf(Lib071.verifySign(packet, verify_key));
		}
	}

	/**
	 * 验证报文签名
	 */
	class Post extends ThreeArgFunction
	{
		/**
		 * @param arg1 Lua Packet 对象
		 * @param arg2 服务器 URL 地址
		 * @param arg3 超时
		 */
		@Override
		public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) {
			LuaTable lua_packet = arg1.checktable();
			Packet071 packet = (Packet071) lua_packet.get("_object")
					.checkuserdata(Packet071.class);

			String target = arg2.checkjstring();
			URL target_url;
			try {
				target_url = new URL(target);
			} catch (MalformedURLException e) {
				logger.error("参数[{}]不是有效的URL字符串.", target);
				return LuaValue.NIL;
			}
			int timeout = arg3.checkint();

			HttpClient client = new HttpClient(target_url, timeout,
					packet.get("encoding"), true);
			int resp_code = client.doPost(packet.pack());
			if (resp_code == -1) {
				logger.error("发送HTTP POST请求到[{}]失败.", target);
				return LuaValue.NIL;
			}
			if (resp_code != 200) {
				logger.error("服务器响应 HTTP 状态码非 200，不处理响应数据，交易失败.");
				return LuaValue.NIL;
			}
			Packet071 respkt = new Packet071();
			if (!respkt.parse(client.getResponse())) {
				logger.error("解析响应报文失败.");
				return LuaValue.NIL;
			}
			return new LuaPacket(respkt).call();
		}
	}

}
